"""Route planning service."""
from typing import List, Optional
import requests
import math
import logging
from app.models.location import Location
from app.models.poi import POI
from app.models.route import Route
from app.models.weather_data import WeatherData
from app import db
from app.utils.error_handler import (
    retry_with_exponential_backoff,
    safe_api_call,
    NetworkError,
    APIError
)

logger = logging.getLogger(__name__)


class RoutePlannerService:
    """Service for planning walking routes."""
    
    def __init__(self, tencent_map_key: str):
        """Initialize route planner service.
        
        Args:
            tencent_map_key: Tencent Map API key
        """
        self.tencent_map_key = tencent_map_key
        self.tencent_place_url = "https://apis.map.qq.com/ws/place/v1/search"
        self.tencent_direction_url = "https://apis.map.qq.com/ws/direction/v1/walking"
    
    @retry_with_exponential_backoff(max_retries=3, initial_delay=1.0)
    def search_nearby_pois(
        self,
        user_location: Location,
        radius_meters: int = 3000
    ) -> List[POI]:
        """Search for nearby POIs using Tencent Map API with retry.
        
        Args:
            user_location: User's current location
            radius_meters: Search radius in meters
            
        Returns:
            List of nearby POIs
            
        Raises:
            NetworkError: If network request fails after retries
            APIError: If API returns error after retries
        """
        # Call Tencent Map Place API
        params = {
            'key': self.tencent_map_key,
            'boundary': f"nearby({user_location.latitude},{user_location.longitude},{radius_meters})",
            'keyword': '风景名胜,公园,广场,博物馆,纪念馆',
            'page_size': 20,
            'page_index': 1
        }
        
        try:
            response = safe_api_call(
                url=self.tencent_place_url,
                method='GET',
                params=params,
                timeout=10
            )
            
            result = response.json()
            
            if result.get('status') != 0:
                raise APIError(f"Tencent Map API error: {result.get('message', 'Unknown error')}")
            
            pois_data = result.get('data', [])
            pois = []
            
            for poi_data in pois_data:
                # Parse location from Tencent Map format
                location_obj = poi_data.get('location', {})
                if not location_obj:
                    continue
                
                try:
                    lat = float(location_obj.get('lat', 0))
                    lon = float(location_obj.get('lng', 0))
                except (ValueError, AttributeError):
                    continue
                
                # Check if POI already exists in database
                external_id = poi_data.get('id')
                existing_poi = POI.query.filter_by(external_id=external_id).first()
                
                if existing_poi:
                    pois.append(existing_poi)
                else:
                    # Create new POI
                    poi = POI(
                        name=poi_data.get('title', '未知景点'),
                        latitude=lat,
                        longitude=lon,
                        category=poi_data.get('category', '其他'),
                        description=poi_data.get('address', ''),
                        photos=[],
                        rating=None,
                        tags=[],
                        external_id=external_id
                    )
                    db.session.add(poi)
                    pois.append(poi)
            
            # Commit all new POIs
            if pois:
                db.session.commit()
            
            # Score and sort POIs
            scored_pois = []
            for poi in pois:
                score = self._calculate_poi_score(poi, user_location)
                scored_pois.append((score, poi))
            
            scored_pois.sort(key=lambda x: x[0], reverse=True)
            
            logger.info(f"Found {len(pois)} POIs near location")
            return [poi for _, poi in scored_pois]
            
        except (NetworkError, APIError) as e:
            logger.error(f"Failed to search POIs: {str(e)}")
            # Try to return cached POIs from database as fallback
            logger.info("Attempting to use cached POIs from database")
            
            try:
                # Get POIs from database within radius
                all_pois = POI.query.all()
                nearby_pois = []
                
                for poi in all_pois:
                    distance = self._calculate_distance(
                        user_location.latitude, user_location.longitude,
                        poi.latitude, poi.longitude
                    )
                    if distance <= radius_meters:
                        nearby_pois.append(poi)
                
                if nearby_pois:
                    # Score and sort
                    scored_pois = []
                    for poi in nearby_pois:
                        score = self._calculate_poi_score(poi, user_location)
                        scored_pois.append((score, poi))
                    
                    scored_pois.sort(key=lambda x: x[0], reverse=True)
                    logger.warning(f"Using {len(nearby_pois)} cached POIs from database")
                    return [poi for _, poi in scored_pois]
                else:
                    logger.error("No cached POIs available in database")
            except Exception as db_error:
                logger.error(f"Database fallback failed: {str(db_error)}")
            
            # No fallback available, re-raise
            raise
    
    def _calculate_poi_score(self, poi: POI, user_location: Location) -> float:
        """Calculate POI score based on distance and other factors.
        
        Args:
            poi: POI to score
            user_location: User's location
            
        Returns:
            Score (higher is better)
        """
        # Calculate distance
        distance = self._calculate_distance(
            user_location.latitude, user_location.longitude,
            poi.latitude, poi.longitude
        )
        
        # Base score inversely proportional to distance
        # Prefer POIs between 500m and 2000m
        if distance < 500:
            distance_score = distance / 500  # Penalize very close POIs
        elif distance > 2000:
            distance_score = 2000 / distance  # Penalize far POIs
        else:
            distance_score = 1.0
        
        # Category bonus
        category_bonus = 1.0
        preferred_categories = ['风景名胜', '公园', '广场', '博物馆', '纪念馆']
        if any(cat in poi.category for cat in preferred_categories):
            category_bonus = 1.2
        
        # Rating bonus (if available)
        rating_bonus = 1.0
        if poi.rating:
            rating_bonus = 1.0 + (poi.rating / 10.0)  # Max 1.5x bonus for 5-star rating
        
        return distance_score * category_bonus * rating_bonus
    
    @retry_with_exponential_backoff(max_retries=3, initial_delay=1.0)
    def generate_route_to_poi(
        self,
        start_location: Location,
        target_poi: POI,
        weather_data: WeatherData
    ) -> Route:
        """Generate route from start location to target POI with retry.
        
        Args:
            start_location: Starting location
            target_poi: Target POI
            weather_data: Current weather data
            
        Returns:
            Generated route
            
        Raises:
            NetworkError: If network request fails after retries
            APIError: If API returns error after retries
        """
        # Call Tencent Map Walking Direction API
        params = {
            'key': self.tencent_map_key,
            'from': f"{start_location.latitude},{start_location.longitude}",
            'to': f"{target_poi.latitude},{target_poi.longitude}"
        }
        
        try:
            response = safe_api_call(
                url=self.tencent_direction_url,
                method='GET',
                params=params,
                timeout=10
            )
            
            result = response.json()
            
            if result.get('status') != 0:
                raise APIError(f"Tencent Map API error: {result.get('message', 'Unknown error')}")
            
            route_result = result.get('result', {})
            routes = route_result.get('routes', [])
            
            if not routes:
                raise APIError("No route found")
            
            # Use the first route
            route_data = routes[0]
            
            # Parse waypoints from polyline
            waypoints = []
            polyline = route_data.get('polyline', [])
            # Tencent Map returns polyline as compressed coordinate array
            for i in range(0, len(polyline), 2):
                if i + 1 < len(polyline):
                    lat = polyline[i] / 1000000.0
                    lon = polyline[i + 1] / 1000000.0
                    waypoints.append({'latitude': lat, 'longitude': lon})
            
            # Create route
            route = Route(
                start_latitude=start_location.latitude,
                start_longitude=start_location.longitude,
                end_latitude=target_poi.latitude,
                end_longitude=target_poi.longitude,
                waypoints=waypoints,
                distance_meters=float(route_data.get('distance', 0)),
                estimated_duration=int(float(route_data.get('duration', 0)) / 60),
                target_poi_id=target_poi.id
            )
            
            db.session.add(route)
            db.session.commit()
            
            logger.info(f"Generated route to POI {target_poi.id}")
            return route
            
        except (NetworkError, APIError) as e:
            logger.error(f"Failed to generate route: {str(e)}")
            # Fallback: create simple straight-line route
            logger.warning("Creating fallback straight-line route")
            
            try:
                distance = self._calculate_distance(
                    start_location.latitude, start_location.longitude,
                    target_poi.latitude, target_poi.longitude
                )
                
                # Simple waypoints: start and end
                waypoints = [
                    {'latitude': start_location.latitude, 'longitude': start_location.longitude},
                    {'latitude': target_poi.latitude, 'longitude': target_poi.longitude}
                ]
                
                # Estimate duration: assume 5 km/h walking speed
                estimated_duration = int((distance / 1000) / 5 * 60)  # minutes
                
                route = Route(
                    start_latitude=start_location.latitude,
                    start_longitude=start_location.longitude,
                    end_latitude=target_poi.latitude,
                    end_longitude=target_poi.longitude,
                    waypoints=waypoints,
                    distance_meters=distance,
                    estimated_duration=estimated_duration,
                    target_poi_id=target_poi.id
                )
                
                db.session.add(route)
                db.session.commit()
                
                logger.warning(f"Created fallback route to POI {target_poi.id}")
                return route
                
            except Exception as fallback_error:
                logger.error(f"Fallback route creation failed: {str(fallback_error)}")
                raise e  # Re-raise original error
    
    def replan_route(
        self,
        current_location: Location,
        choice: str,
        home_location: Location,
        available_pois: List[POI]
    ) -> Route:
        """Replan route based on user choice.
        
        Args:
            current_location: Current location
            choice: 'home' or 'next'
            home_location: User's home location
            available_pois: Available POIs for next destination
            
        Returns:
            New route
        """
        if choice == 'home':
            # Generate route back home
            # Create a temporary POI for home location
            home_poi = POI(
                name='家',
                latitude=home_location.latitude,
                longitude=home_location.longitude,
                category='住宅',
                description='用户家位置'
            )
            
            # Generate route to home (without weather data as it's not critical for going home)
            from app.models.weather_data import WeatherData
            from datetime import datetime
            dummy_weather = WeatherData(
                temperature=20.0,
                condition='晴',
                humidity=50,
                wind_speed=10.0,
                aqi=None,
                timestamp=datetime.utcnow()
            )
            
            # Call Tencent Map Walking Direction API
            params = {
                'key': self.tencent_map_key,
                'from': f"{current_location.latitude},{current_location.longitude}",
                'to': f"{home_location.latitude},{home_location.longitude}"
            }
            
            response = requests.get(self.tencent_direction_url, params=params, timeout=10)
            response.raise_for_status()
            
            result = response.json()
            
            if result.get('status') != 0:
                raise Exception(f"Tencent Map API error: {result.get('message', 'Unknown error')}")
            
            route_result = result.get('result', {})
            routes = route_result.get('routes', [])
            
            if not routes:
                raise Exception("No route found")
            
            route_data = routes[0]
            
            # Parse waypoints from polyline
            waypoints = []
            polyline = route_data.get('polyline', [])
            for i in range(0, len(polyline), 2):
                if i + 1 < len(polyline):
                    lat = polyline[i] / 1000000.0
                    lon = polyline[i + 1] / 1000000.0
                    waypoints.append({'latitude': lat, 'longitude': lon})
            
            route = Route(
                start_latitude=current_location.latitude,
                start_longitude=current_location.longitude,
                end_latitude=home_location.latitude,
                end_longitude=home_location.longitude,
                waypoints=waypoints,
                distance_meters=float(route_data.get('distance', 0)),
                estimated_duration=int(float(route_data.get('duration', 0)) / 60),
                target_poi_id=None  # No POI for home
            )
            
            db.session.add(route)
            db.session.commit()
            
            return route
            
        elif choice == 'next':
            # Find the best next POI
            if not available_pois:
                # Search for new POIs if none provided
                available_pois = self.search_nearby_pois(current_location)
            
            if not available_pois:
                raise Exception("No POIs available for next destination")
            
            # Score POIs and select the best one
            best_poi = None
            best_score = -1
            
            for poi in available_pois:
                score = self._calculate_poi_score(poi, current_location)
                if score > best_score:
                    best_score = score
                    best_poi = poi
            
            if not best_poi:
                raise Exception("Could not select next POI")
            
            # Generate route to next POI
            from app.models.weather_data import WeatherData
            from datetime import datetime
            dummy_weather = WeatherData(
                temperature=20.0,
                condition='晴',
                humidity=50,
                wind_speed=10.0,
                aqi=None,
                timestamp=datetime.utcnow()
            )
            
            return self.generate_route_to_poi(current_location, best_poi, dummy_weather)
        
        else:
            raise ValueError(f"Invalid choice: {choice}. Must be 'home' or 'next'")
    
    def _calculate_distance(self, lat1: float, lon1: float, lat2: float, lon2: float) -> float:
        """Calculate distance between two points using Haversine formula.
        
        Args:
            lat1: Latitude of point 1
            lon1: Longitude of point 1
            lat2: Latitude of point 2
            lon2: Longitude of point 2
            
        Returns:
            Distance in meters
        """
        R = 6371000  # Earth radius in meters
        
        phi1 = math.radians(lat1)
        phi2 = math.radians(lat2)
        delta_phi = math.radians(lat2 - lat1)
        delta_lambda = math.radians(lon2 - lon1)
        
        a = math.sin(delta_phi / 2) ** 2 + \
            math.cos(phi1) * math.cos(phi2) * math.sin(delta_lambda / 2) ** 2
        c = 2 * math.atan2(math.sqrt(a), math.sqrt(1 - a))
        
        return R * c
